package jparchess.gui;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.border.Border;

import org.apache.xerces.impl.RevalidationHandler;

import com.google.gwt.dom.client.Style.Cursor;


import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

import jparchess.command.Game;
import jparchess.engine.D;
import jparchess.engine.Move;
import jparchess.engine.Search;

import javax.swing.JMenuBar;
import javax.swing.JMenu;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.JTextArea;
import javax.swing.JTable;

public class BoardGUI extends JFrame {
	
	JMenu mnStart;
	JLabel statusLabel;
	JPanel chessBoard;
	JPanel viewPanel;
	JTextArea textArea;
	ChessSquare[] boardPanel = new ChessSquare[64];
	JLabel[] boardText = new JLabel[64];
	JLabel[] picLabel  = new JLabel[64];
	Game game;
	int selectedMoveFromSquare = -1;
    ArrayList<Move> showMoveList = new ArrayList<Move>();
    
	int cpuResult;
	private JTable moveListTable;
	
	public BoardGUI(Game game) {
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setTitle("JParChess");
		this.game = game;
		GridBagLayout gridBagLayout = new GridBagLayout();
		gridBagLayout.columnWidths = new int[]{200, 200, 0, 0};
		gridBagLayout.rowHeights = new int[]{294, 0};
		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
		getContentPane().setLayout(gridBagLayout);
		viewPanel = new JPanel();
		GridBagConstraints gbc_viewPanel = new GridBagConstraints();
		gbc_viewPanel.insets = new Insets(0, 0, 0, 5);
		gbc_viewPanel.fill = GridBagConstraints.BOTH;
		gbc_viewPanel.gridx = 1;
		gbc_viewPanel.gridy = 0;
		getContentPane().add(viewPanel, gbc_viewPanel);
		GridBagLayout gbl_viewPanel = new GridBagLayout();
		gbl_viewPanel.columnWidths = new int[]{195, 0};
		gbl_viewPanel.rowHeights = new int[]{294, 0};
		gbl_viewPanel.columnWeights = new double[]{0.0, Double.MIN_VALUE};
		gbl_viewPanel.rowWeights = new double[]{0.0, Double.MIN_VALUE};
		viewPanel.setLayout(gbl_viewPanel);
		
		JPanel boardInfoPanel = new JPanel();
		GridBagConstraints gbc_boardInfoPanel = new GridBagConstraints();
		gbc_boardInfoPanel.fill = GridBagConstraints.BOTH;
		gbc_boardInfoPanel.gridx = 0;
		gbc_boardInfoPanel.gridy = 0;
		viewPanel.add(boardInfoPanel, gbc_boardInfoPanel);
		GridBagLayout gbl_boardInfoPanel = new GridBagLayout();
		gbl_boardInfoPanel.columnWidths = new int[]{65, 65, 65, 0};
		gbl_boardInfoPanel.rowHeights = new int[]{294, 0, 0, 0};
		gbl_boardInfoPanel.columnWeights = new double[]{0.0, 0.0, 1.0, Double.MIN_VALUE};
		gbl_boardInfoPanel.rowWeights = new double[]{1.0, 0.0, 0.0, Double.MIN_VALUE};
		boardInfoPanel.setLayout(gbl_boardInfoPanel);
		
		JLabel lblBoardinfo = new JLabel("Board stats");
		GridBagConstraints gbc_lblBoardinfo = new GridBagConstraints();
		gbc_lblBoardinfo.weightx = 1.0;
		gbc_lblBoardinfo.anchor = GridBagConstraints.NORTH;
		gbc_lblBoardinfo.fill = GridBagConstraints.BOTH;
		gbc_lblBoardinfo.insets = new Insets(0, 0, 5, 5);
		gbc_lblBoardinfo.gridx = 0;
		gbc_lblBoardinfo.gridy = 0;
		boardInfoPanel.add(lblBoardinfo, gbc_lblBoardinfo);
		
		statusLabel = new JLabel("White to move       ");
		GridBagConstraints gbc_statusLabel = new GridBagConstraints();
		gbc_statusLabel.weightx = 1.0;
		gbc_statusLabel.anchor = GridBagConstraints.WEST;
		gbc_statusLabel.insets = new Insets(0, 0, 5, 5);
		gbc_statusLabel.gridx = 1;
		gbc_statusLabel.gridy = 0;
		boardInfoPanel.add(statusLabel, gbc_statusLabel);
		
		moveListTable = new JTable();
		GridBagConstraints gbc_moveListTable = new GridBagConstraints();
		gbc_moveListTable.insets = new Insets(0, 0, 5, 0);
		gbc_moveListTable.fill = GridBagConstraints.BOTH;
		gbc_moveListTable.gridx = 2;
		gbc_moveListTable.gridy = 0;
		boardInfoPanel.add(moveListTable, gbc_moveListTable);
		
		textArea = new JTextArea();
		GridBagConstraints gbc_textArea = new GridBagConstraints();
		gbc_textArea.insets = new Insets(0, 0, 5, 5);
		gbc_textArea.weightx = 1.0;
		gbc_textArea.weighty = 1.0;
		gbc_textArea.fill = GridBagConstraints.BOTH;
		gbc_textArea.gridx = 0;
		gbc_textArea.gridy = 1;
		boardInfoPanel.add(textArea, gbc_textArea);
		
			chessBoard = new JPanel();
			
		GridBagConstraints gbc_chessBoard = new GridBagConstraints();
		gbc_chessBoard.weighty = 1.0;
		gbc_chessBoard.weightx = 1.0;
		gbc_chessBoard.fill = GridBagConstraints.BOTH;
		gbc_chessBoard.insets = new Insets(0, 0, 0, 5);
		gbc_chessBoard.gridx = 0;
		gbc_chessBoard.gridy = 0;
		getContentPane().add(chessBoard, gbc_chessBoard);
		chessBoard.setLayout(new GridLayout(8, 8, 0, 0));
		
		
		 JMenuBar menuBar = new JMenuBar();
	        mnStart = new JMenu("Start");
	        mnStart.setMnemonic(KeyEvent.VK_ALT);
	        JMenuItem mntmNewGame = new JMenuItem("New Game",
	        		KeyEvent.VK_N);
	        mntmNewGame.addActionListener(new ActionListener() {
	        	public void actionPerformed(ActionEvent arg0) {
	        		newGame();
	        	}
	        });

	        mnStart.add(mntmNewGame);
	        menuBar.add(mnStart);
	        
	        JMenuItem mntmCpuMove = new JMenuItem("CPU Move",
                    KeyEvent.VK_C);
	        mntmCpuMove.addActionListener(new ActionListener() {
	        	public void actionPerformed(ActionEvent arg0) {
	        		cpuMoveOnNewThread();
	        	}
	        });
	        
	        JMenuItem mntmUpdateBoard = new JMenuItem("Update Board",
                    KeyEvent.VK_B);
	        mntmUpdateBoard.addActionListener(new ActionListener() {
	        	public void actionPerformed(ActionEvent arg0) {
	        		updateBoard();
	        		updateView(false, 0);
	        	}
	        });
	        mnStart.add(mntmUpdateBoard);
	        mnStart.add(mntmCpuMove);
	        
	        JMenuItem mntmUndoMove = new JMenuItem("Undo Move", KeyEvent.VK_U);
	        mntmUndoMove.addActionListener(new ActionListener() {
	        	public void actionPerformed(ActionEvent arg0) {
	        		undoMove();
	        	}
	        });
	        mnStart.add(mntmUndoMove);

	       // setContentPane(contentPane);
	        setJMenuBar(menuBar);
	}
	
	protected void undoMove() {
		game.search.moveMaker.undoMove(null);
		updateBoard();
		updateView(false, 0);
	}

	protected void cpuMoveOnNewThread() {
		new SwingWorker<Move, Integer>() {
		    @Override
		    public Move doInBackground() {
		    	java.awt.Cursor c = new java.awt.Cursor( java.awt.Cursor.WAIT_CURSOR);
		    	chessBoard.setCursor(c);
		    	mnStart.setCursor(c);
		    	statusLabel.setText("Computer is thinking");
		    	
		    	Move move = null;
		    	for (int i=1; i<=game.search.searchDepth; i++) {
		    		move = game.search.think(i);
		    		this.publish(i);
		    	}
		    	
		        return move;
		    }
		    
		    @Override
 			protected final void process(final List<Integer> chunks) {
				updateView(true, chunks.get(0));
 			}

		    @Override
		    public void done() {
		    	try {
					game.search.moveMaker.doMove(get());
					updateBoard();
					updateView(true, game.search.searchDepth);
					java.awt.Cursor c = new java.awt.Cursor( java.awt.Cursor.DEFAULT_CURSOR);
			    	chessBoard.setCursor(c);
			    	mnStart.setCursor(c);
			    	statusLabel.setText("Your move.......");
	
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ExecutionException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		    }
		}.execute();

	}
	
	protected void newGame() {
		// close and open new game
		game.terminate();
		game.start();
	}

	private void showPossibleMoves(int guiIndex) {
	    showMoveList = new ArrayList<Move>();
		game.search.moveGenerator.generateMoves();
		
		// get flags if play has advanced
		if (game.gameLine.flagIndex > 0) {			
			game.board.whiteCanCastle00 = game.gameLine.whiteCanCastle00.get(game.gameLine.whiteCanCastle00.size() - 1);
			game.board.whiteCanCastle000 = game.gameLine.whiteCanCastle000.get(game.gameLine.whiteCanCastle000.size() - 1);
			game.board.blackCanCastle00 = game.gameLine.blackCanCastle00.get(game.gameLine.blackCanCastle00.size() - 1);
			game.board.blackCanCastle000 = game.gameLine.blackCanCastle000.get(game.gameLine.blackCanCastle000.size() - 1);
			game.board.enPassantSquare = game.gameLine.enPassantSquare.get(game.gameLine.enPassantSquare.size() - 1);
		}
		
		
		for (Move move : game.search.moveGenerator.moveList) {
			if (move.from == boardIndex(guiIndex)) {
				showMoveList.add(move);
			}
		}
		
		for (Move move : showMoveList)
			boardPanel[guiIndex(move.to)].setBackground(Color.green);
				
	}
	
	public void initBoard() {
		for (int i=0; i<64; i++) {
			boardPanel[i] = new ChessSquare();
			boardPanel[i].setToolTipText(String.valueOf(i));
			
			boardText[i] = new JLabel();
			//boardPanel[i].add(boardText[i]);
			picLabel[i] = new JLabel();
			boardPanel[i].add(picLabel[i]);
			
			boardPanel[i].addMouseListener(new MouseListener() {
				
				@Override
				public void mouseReleased(MouseEvent e) {
					// TODO Auto-generated method stub
					
				}
				
				@Override
				public void mousePressed(MouseEvent e) {
					ChessSquare square = (ChessSquare) e.getSource();
					if (square.isSelected == false) {					
						// do we do a move?
						if (boardIndex(selectedMoveFromSquare) >= D.A1) {
							for (Move move : showMoveList) {
								if (move.to == boardIndex(Integer.valueOf(square.getToolTipText())))  {
									game.search.moveMaker.doMove(move);
									D.log(move);
									updateBoard();
									updateView(false, 0);
									selectedMoveFromSquare = -1;
									
									cpuMoveOnNewThread();
									return;
								}
							}
							// no target square found
							paintClearBoard();
							updateBoard();
							System.out.println("not a target square");
							return;
						}
						
						System.out.println(square.getToolTipText());
						square.setBackground(Color.orange);

						// show possible moves
						showPossibleMoves(Integer.valueOf(square.getToolTipText()));
						square.isSelected = true;
						selectedMoveFromSquare = Integer.valueOf(square.getToolTipText());
					}
					else {		
						// revert back all selected squares
						paintClearBoard();
						updateBoard();
						
					}
				}
				
				@Override
				public void mouseExited(MouseEvent e) {
					// TODO Auto-generated method stub
					
				}
				
				@Override
				public void mouseEntered(MouseEvent e) {
					// TODO Auto-generated method stub
					
				}
				
				@Override
				public void mouseClicked(MouseEvent e) {
					// TODO Auto-generated method stub
					
				}
			});
						
			chessBoard.add(boardPanel[i]);
		}
		paintClearBoard();	
	}


	public int guiIndex(int index) {
		int rank = (63 - index) / 8;
		int file = (63 - index) % 8;
		return (8*rank + (7-file));
	}
	
	public int boardIndex(int index) {
		if (index < D.A1) return -1;
		int rank = (63 - index) / 8;
		int file = (63 - index) % 8;
		return (8*rank + (7-file));
	}
	
	public void paintClearBoard() {
		for (int i=0; i<64;i++)	{
			boardPanel[i].setBorder(BorderFactory.createLineBorder(Color.black));
			boardText[i].setText("");
			picLabel[i].setVisible(false);
			// clear selected squares
			boardPanel[i].isSelected = false;
			selectedMoveFromSquare = -1;
		}
		// revert back all changes
		for (int i=D.A1; i<=D.H8; i++) {
			if (((Math.floor(i / 8) % 2) == 0))
				if (i % 2 == 0) boardPanel[i].setBackground(new Color(255, 206, 158));
			    else boardPanel[i].setBackground(new Color(209, 139, 71));
		    else
		    	if (i % 2 == 1) boardPanel[i].setBackground(new Color(255, 206, 158));
			 	else boardPanel[i].setBackground(new Color(209, 139, 71));						
		}				
	}

	
	public void updateView(boolean pv, int ply) {
		String infoText;
		infoText = "m-nb : " + game.gameLine.flagIndex + "\n";
		infoText += "material : " + game.board.material + "\n";
		//infoText = "best move : " + D.logToView(game.search.bestMove) + "\n";
		infoText += "with move score : " + game.search.absBestMoveScore + "\n";
		infoText += "ply-depth : " + ply + "\n";
		infoText += "search-nodes : " + game.search.searchNodes + "\n";
		if (pv) { // true then we should display the principal variation
			infoText += "****************" + "\n";
			boolean moveSide = D.BLACK_TO_MOVE;//game.board.sideToMove;
			boolean newLine = true;
			for (int p = 0; p < ply; p++) {	
				if (newLine) infoText += String.valueOf((p+1)/2 + 1) + ". ";
				if (moveSide == D.BLACK_TO_MOVE) {
					if (newLine) infoText += "*** " + D.portableGameNotation(game.search.triangularArray[p][p]) + "\n";	
					else infoText += D.portableGameNotation(game.search.triangularArray[p][p]) + "\n";					
					newLine = true;
				}
				else {				
					infoText += D.portableGameNotation(game.search.triangularArray[p][p]) + " ";
					newLine = false;
				}
				
				moveSide = !moveSide;
			}
				
		}
			
		
//		infoText += "enP-Square : " +game.board.enPassantSquare + "\n";
//		infoText += "w-00 : " +game.board.whiteCanCastle00 + "\n";
//		infoText += "w-000 : " +game.board.whiteCanCastle000 + "\n";
//		infoText += "b-00 : " +game.board.blackCanCastle00 + "\n";
//		infoText += "b-000 : " +game.board.blackCanCastle000 + "\n";
//		infoText += "********************************************\n";
//		for (int i=0; i<= game.gameLine.flagIndex; i++) {
//			infoText += "w-00 : " +game.gameLine.whiteCanCastle00.get(i) + "\n";
//		}
		
		textArea.setText(infoText);
	}
	
	public void updateBoard() {
		paintClearBoard();
		
		for (int i=0; i<64;i++)
		{	
			String tileSizeString = "";
			int height = ((int) this.getSize().getHeight());
			int width =  ((int) this.getSize().getWidth());
			
				if (height < 500) {
					tileSizeString = tileSizeString.concat(String.valueOf((int) (height / 10)));
				}
				if (height >= 500 && height < 600) {
					tileSizeString = tileSizeString.concat("52");
				}
				if (height >= 600 && height < 700) {
					tileSizeString = tileSizeString.concat("64");
				}
				if (height >= 700 && height < 800) {
					tileSizeString = tileSizeString.concat("72");
				}
				if (height >= 800 && height < 900) {
					tileSizeString = tileSizeString.concat("64");
				}
				if (height >= 900 && height < 1000) {
					tileSizeString = tileSizeString.concat("96");
				}
				if (height >= 1000 && height < 1200) {
					tileSizeString = tileSizeString.concat("112");
				}
				if (height >= 1200 && height < 1400) {
					tileSizeString = tileSizeString.concat("128");
				}
				if (height >= 1400) {
					tileSizeString = tileSizeString.concat("144");
				}
				
			
			tileSizeString = tileSizeString.concat("/");
			String tileFileString = "tiles/";
			BufferedImage myPicture = null;
			try {
				switch (game.board.square[i]) {
					case D.WHITE_PAWN:
						tileFileString = tileFileString.concat(tileSizeString + "wp");
						break;
					case D.WHITE_KNIGHT:
						tileFileString = tileFileString.concat(tileSizeString + "wn");
						break;
					case D.WHITE_BISHOP:
						tileFileString = tileFileString.concat(tileSizeString + "wb");
						break;
					case D.WHITE_ROOK:
						tileFileString = tileFileString.concat(tileSizeString + "wr");
						break;
					case D.WHITE_QUEEN:
						tileFileString = tileFileString.concat(tileSizeString + "wq");
						break;
					case D.WHITE_KING:
						tileFileString = tileFileString.concat(tileSizeString + "wk");
						break;
					case D.BLACK_PAWN:
						tileFileString = tileFileString.concat(tileSizeString + "bp");
						break;
					case D.BLACK_KNIGHT:
						tileFileString = tileFileString.concat(tileSizeString + "bn");
						break;
					case D.BLACK_BISHOP:
						tileFileString = tileFileString.concat(tileSizeString + "bb");
						break;
					case D.BLACK_ROOK:
						tileFileString = tileFileString.concat(tileSizeString + "br");
						break;
					case D.BLACK_QUEEN:
						tileFileString = tileFileString.concat(tileSizeString + "bq");
						break;
					case D.BLACK_KING:
						tileFileString = tileFileString.concat(tileSizeString + "bk");
						break;			
	
					default:
						break;
				}
				if (!tileFileString.endsWith("/")) { // we have found a piece and want to plot it
					tileFileString = tileFileString.concat(".png");
					
					myPicture = ImageIO.read(new File(tileFileString));
					Image image = TransparencyUtil.makeColorTransparent(myPicture, Color.WHITE);
					myPicture.getGraphics().drawImage(image, 0 , 0, null);
					
					picLabel[guiIndex(i)].setIcon(new ImageIcon (myPicture));
					picLabel[guiIndex(i)].setVisible(true);
					boardPanel[guiIndex(i)].revalidate();
				}
				
				
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		
		
		
		
		
		
		for (int i=0; i<64;i++)
		{				
			boardText[i].setFont(new Font("Arial", Font.BOLD, 40));
			switch (game.board.square[i]) {
			case D.WHITE_PAWN:
				boardText[guiIndex(i)].setText("P");
				break;
			case D.WHITE_KNIGHT:
				boardText[guiIndex(i)].setText("N");
				break;
			case D.WHITE_BISHOP:
				boardText[guiIndex(i)].setText("B");
				break;
			case D.WHITE_ROOK:
				boardText[guiIndex(i)].setText("R");
				break;
			case D.WHITE_QUEEN:
				boardText[guiIndex(i)].setText("Q");
				break;
			case D.WHITE_KING:
				boardText[guiIndex(i)].setText("K");
				break;
			case D.BLACK_PAWN:
				boardText[guiIndex(i)].setText("p");
				break;
			case D.BLACK_KNIGHT:
				boardText[guiIndex(i)].setText("n");
				break;
			case D.BLACK_BISHOP:
				boardText[guiIndex(i)].setText("b");
				break;
			case D.BLACK_ROOK:
				boardText[guiIndex(i)].setText("r");
				break;
			case D.BLACK_QUEEN:
				boardText[guiIndex(i)].setText("q");
				break;
			case D.BLACK_KING:
				boardText[guiIndex(i)].setText("k");
				break;			

			default:
				break;
			}
								
		}
	}
}
